package com.tools.db.jdbc;

import com.tools.common.object.DateKit;
import com.tools.common.object.Note;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandlerRegistry;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.TemporalAccessor;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;


/**
 * MyBatis 公共拦截器
 * 拦截所有的查询、更新、批处理方法
 * */
@Note("MyBatis 公共拦截器。" +
        "拦截所有的查询、更新、批处理方法")
@Intercepts(@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}))
@Slf4j
public class CommonInterceptor implements Interceptor {

    @Note("SQL 日志打印的分割线")
    private static final String BORDER = "-------------------------";

    /* *******************************************************************************************
     *
     *          拦截逻辑
     *
     * *******************************************************************************************
     * */

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaStatementHandler = MetaObject.forObject(statementHandler,
                SystemMetaObject.DEFAULT_OBJECT_FACTORY,
                SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
                new DefaultReflectorFactory());
        MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
        String sqlId = mappedStatement.getId();
        //获取参数
        Object parameter = metaStatementHandler.getValue("delegate.boundSql.parameterObject");
        //获取原始SQL
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
        String srcSql = boundSql.getSql();
        //如果第一个参数是 PagingBean 类型且开启了自动分页，则开始拼接分页 SQL 和参数
        if(parameter != null) {
            if(parameter instanceof MapperMethod.ParamMap) {
                MapperMethod.ParamMap<?> map = (MapperMethod.ParamMap<?>) parameter;
                Object param1 = null;
                if(map.containsKey("param1")) {
                    param1 = map.get("param1");
                } else if(map.containsKey("pagingBean")) {
                    param1 = map.get("pagingBean");
                }
                if(param1 instanceof PagingBean) {
                    PagingBean pagingBean = (PagingBean) param1;
                    if(pagingBean.isAutoPaging()) {
                        return private_paging(sqlId, pagingBean, srcSql, invocation, metaStatementHandler);
                    }
                }
            }
            else if(parameter instanceof PagingBean) {
                PagingBean pagingBean = (PagingBean) parameter;
                if(pagingBean.isAutoPaging()) {
                    return private_paging(sqlId, pagingBean, srcSql, invocation, metaStatementHandler);
                }
            }
        }
        //SQL 参数替换 ? ，并计算耗时，最后打印日志
        String sql = private_getLogSqlString(boundSql, mappedStatement.getConfiguration());
        long start = System.currentTimeMillis();
        Object result = invocation.proceed();
        long end = System.currentTimeMillis();

        log.info("\n{} [{}] {}\n【SQL】 ==> : {}\n【ExecTime】: {} ms\n{}{}{}\n",
                BORDER, mappedStatement.getId(), BORDER, sql, end - start, BORDER, BORDER, BORDER);
        return result;
    }

    /* *******************************************************************************************
     *
     *          私有逻辑
     *
     * *******************************************************************************************
     * */


    @Note("分页逻辑")
    private Object private_paging(String sqlId, PagingBean pagingBean, String srcSql, Invocation invocation, MetaObject metaStatementHandler) throws Exception {
        //总数
        String countSql = "SELECT COUNT(1) FROM (" + srcSql + ") t";
        Connection conn = (Connection) invocation.getArgs()[0];
        PreparedStatement countStatement = conn.prepareStatement(countSql);
        ParameterHandler parameterHandler = (ParameterHandler) metaStatementHandler.getValue("delegate.parameterHandler");
        parameterHandler.setParameters(countStatement);
        long start = System.currentTimeMillis();
        ResultSet resultSet = countStatement.executeQuery();
        long end = System.currentTimeMillis();
        if(resultSet.next()) {
            long count = resultSet.getLong(1);
            pagingBean.setTotal(count);
            log.info("\n{} [{}] {}\n【SQL】 ==> : {}\n【ExecTime】: {}\n【Result】: {}\n",
                    BORDER, sqlId, BORDER, countSql, end - start, count);
        } else {
            log.info("\n{} [{}] {}\n【SQL】 ==> : {}\n【ExecTime】: {}\n",
                    BORDER, sqlId, BORDER, countSql, end - start);
        }
        resultSet.close();
        countStatement.close();
        //分页
        LinkedHashMap<String, SortEnum> sortMap = pagingBean.getSortMap();
        String sortSQL = null;
        if(sortMap != null && !sortMap.isEmpty()) {
            StringBuilder builder = new StringBuilder();
            for (Map.Entry<String, SortEnum> e : sortMap.entrySet()) {
                builder.append(", ").append(e.getKey()).append(' ').append(e.getValue().getValue());
            }
            sortSQL = " ORDER BY" + builder.substring(1);
        }
        StringBuilder finalSQL = new StringBuilder(srcSql);
        if(sortSQL != null) finalSQL.append(sortSQL);
        finalSQL.append(" LIMIT ").append(pagingBean.getOffset()).append(", ").append(pagingBean.getPageSize());
        metaStatementHandler.setValue("delegate.boundSql.sql", finalSQL.toString());
        long s = System.currentTimeMillis();
        Object result = invocation.proceed();
        long e = System.currentTimeMillis();
        log.info("\n{} [{}] {}\n【SQL】 ==> : {}\n【ExecTime】: {}\n",
                BORDER, sqlId, BORDER, finalSQL, e - s);
        return result;
    }


    @Note("SQL 参数替换逻辑")
    private static String private_getLogSqlString(BoundSql boundSql, Configuration configuration) {
        // 获取mapper里面方法上的参数
        Object sqlParameter = boundSql.getParameterObject();
        // sql语句里面需要的参数
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if(parameterMappings == null || parameterMappings.isEmpty() || sqlParameter == null) {
            return boundSql.getSql().replaceAll("\\s+", " ");
        }
        // sql语句里面的?替换成真实的参数
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        String sql = boundSql.getSql();
        if (typeHandlerRegistry.hasTypeHandler(sqlParameter.getClass())) {
            return sql.replaceFirst("\\?", private_getParameterValue(sqlParameter))
                    .replaceAll("\\s+", " ");
        }
        MetaObject metaObject = configuration.newMetaObject(sqlParameter);
        for (ParameterMapping parameterMapping : parameterMappings) {
            // 按顺序把?替换成对应的值
            String propertyName = parameterMapping.getProperty();
            if (metaObject.hasGetter(propertyName)) {
                Object obj = metaObject.getValue(propertyName);
                sql = sql.replaceFirst("\\?", private_getParameterValue(obj));
                continue;
            }
            if (boundSql.hasAdditionalParameter(propertyName)) {
                Object obj = boundSql.getAdditionalParameter(propertyName);
                sql = sql.replaceFirst("\\?", private_getParameterValue(obj));
            }
        }
        return sql.replaceAll("\\s+", " ");
    }


    @Note("将参数转为 SQL 语句的字符串形式")
    private static String private_getParameterValue(Object obj) {
        if(obj == null) return "null";
        if (obj instanceof String || obj instanceof Character) {
            return  '\'' + obj.toString() + '\'';
        }
        if (obj instanceof Number || obj instanceof Boolean || obj instanceof Enum) {
            return obj.toString();
        }
        if(obj instanceof Date) {
            return DateKit.format((Date) obj);
        }
        if(obj instanceof LocalDateTime) {
            return DateKit.format((LocalDateTime) obj);
        }
        if(obj instanceof LocalDate) {
            return DateKit.format((LocalDate) obj);
        }
        if(obj instanceof LocalTime) {
            return DateKit.format((LocalTime) obj);
        }
        return obj.toString();
    }

}
